Skip to content

census: the call row carries the demanded set, so served != lane means what it says again (#969) - #1796

Open
7vignesh wants to merge 9 commits into
Agent-Field:devfrom
7vignesh:feat/969-demanded-lanes-on-the-row
Open

7vignesh wants to merge 9 commits into
Agent-Field:devfrom
7vignesh:feat/969-demanded-lanes-on-the-row

Conversation

@7vignesh

@7vignesh 7vignesh commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What changed

Since #937 the chooser demands the SET it admitted - provider.only with allow_fallbacks: false over every machine that survived the gate, the prune, the ceiling and the quality floor - and the router must stay inside that set while being free to pick which member serves. But the call log's Record.Lane is one machine (the ranked head), so a row where served != lane can no longer tell "the router chose a different member of the set we admitted" from "the router went somewhere we never named". The census family built on it (asked ≠ served) quietly changed what it measures (#969).

  • internal/calllog: add Record.Lanes []string (json:"lanes,omitempty"), the whole demanded set. callrows.Row embeds calllog.Record, so the census reads it the day it lands.
  • internal/provider/calllog.go: demandedLanes(knobs) fills record.Lanes from knobs.laneChoice.Only where record.Lane is filled, under the same discipline as soleDemandedLane (only while the demand is still being sent, never off knobs the widen relaxed). Empty when the request demanded nothing or demanded exactly one - the single case Lane alone already answers, and the state of every pre-Lanes: the chooser demands the set it admitted, instead of suggesting it #937 row.
  • cmd/codeaf-census/report.go: servedWasAdmitted - a row with a set agrees when served is a MEMBER of it; a row without one agrees when served matches the single ranked name. Only served landing outside counts as asked ≠ served.

Scoped to the first half of the issue. The cmd/*-replay / Policy.Demand widening the issue names as the second half is deliberately left out - it is only worth doing once the log can say whether the served machine was inside a set, which this change is.

How it was checked

  • go test ./internal/provider/ -run 'TestADemandedSetLeavesEveryAdmittedMachineOnTheRow|TestASingleMachineDemandLeavesNoSetOnTheRow' - end-to-end through pacedPair (two believed-in machines behind one model, from the issue's named rig): one real demanded call writes a row carrying both admitted machines in lanes, the ranked head in lane, and served inside the set. Single-machine and no-demand calls write no set.
  • go test ./cmd/codeaf-census/ -run TestServedInsideTheDemandedSetIsNotAskedNotServed - a log with three rows (served inside the set, served outside a lone name, served outside the set) reports asked ≠ served = 2 of 3, counting the in-set row as agreement.
  • go test ./internal/calllog/ -short, go test ./cmd/codeaf-census/ - green.
  • go vet and gofmt clean on all touched files; go build ./internal/calllog/ ./internal/provider/ ./cmd/codeaf-census/ succeeds.

Note: the full internal/calllog suite and the full internal/provider suite each have one failure on this Windows box that reproduces on clean dev with my changes stashed - TestNoTestInTheTreeWritesIntoTheLedgerOfWhoeverRanIt (a canary that runs other packages as children, several of which don't build on Windows: internal/session is //go:build !windows, others hit SQLite out of memory) and TestAFiredHedgeAndARefusedHedgeAreBothDecisions (a TempDir cleanup race on a trace file still held open). Both are pre-existing and unrelated to this change.

Checklist

  • A change entry - docs/changes/unreleased/1796-demanded-lanes-on-the-row.md.
  • The manual knows about it - no slash command, key, tool, default, limit or refusal moved; this refines an internal census metric with no user-facing manual claim. The dated census snapshot docs/design/recovery/census-20260910.md is a captured record and left as written.
  • No new line in .github/known-red.txt.
  • Only my own paths are staged.

Closes #969.

7vignesh and others added 5 commits October 9, 2026 01:23
…s what it says again (Agent-Field#969)

Since the chooser demands the whole admitted set (provider.only with
fallbacks off), the router may serve any member of it, so Record.Lane
(the ranked head) stopped being the only machine the request would
accept. The census read served != lane as the router going somewhere we
did not choose, which after the set-demand is often the router choosing
a different member of the set we did choose.

Add Record.Lanes []string, filled from laneChoice.Only when the demand
is still being sent, carrying the whole set when it has two or more
members (a one-machine demand is left to Lane alone). The census counts
asked != served only when served fell OUTSIDE the admitted set.

callrows.Row embeds calllog.Record, so the census reads the new field
the day it lands.

@ZeroPoint95 ZeroPoint95 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @7vignesh, thanks for this fix!

I’m requesting changes because demandedLanes records the original choice rather than the demand actually sent for each attempt:

  1. Rescue requests retain the original set. A rescue sends only: [B], but its row records lanes: [A, B]. If A serves that request, the census incorrectly treats it as agreement.

  2. Narrowed retries retain refused machines. After cinnabar refuses, the retry sends only: [tinder], but its row still records lanes: [cinnabar, tinder]. It also retains Lane: cinnabar, so simply omitting Lanes for the singleton would leave incorrect attribution.

Please capture the final composed demand for each attempt and preserve it for both start and finish rows. For singleton demands, either retain the singleton in Lanes or make Lane name the actual demanded machine. Please add wire-versus-log regression tests for both cases.

CI and the full local battery passed. The two provider reproductions and the census violation fixture fail at this head and pass on the base.

7vignesh and others added 2 commits October 14, 2026 04:50
…iginal choice

ZeroPoint95's review on Agent-Field#1796 caught that demandedLanes read the original
laneChoice.Only, so a rescue (sends only:[B]) and a narrowed retry (drops a
refused machine) recorded the whole original set and misattributed Lane.

Capture provider.only as wirePreferences composed it, at encode time, onto
the trace; carry it onto the openAttempt so a finish row written after a
later attempt re-encoded still describes its own. demandedLanes now reads
that composed set, and recordedLane names the single machine a narrowed
attempt really demanded rather than the ranked head it recovered from.

Adds the two wire-versus-log regression tests the review asked for: a rescue
arm records only its one machine, and a narrowed retry does not keep the set
it no longer demands and attributes to the machine it does.
@7vignesh

Copy link
Copy Markdown
Contributor Author

Thanks @ZeroPoint95, you were right on both counts - demandedLanes was reading the original laneChoice.Only rather than the demand each attempt actually sent. Fixed in the latest push:

  • The composed provider.only is now captured at encode time (wire.go, where wire.Provider = c.wirePreferences(...)) onto the call trace, and carried onto the openAttempt so a finish row written after a later attempt re-encoded still describes its own demand (both start and finish rows).
  • demandedLanes reads that composed set, so a rescue arm records only the one machine it went out to (and therefore no set), never the original pair.
  • For the narrowed-retry singleton, recordedLane now names the single machine the attempt actually demanded instead of the ranked head it was recovering from - so a retry that dropped cinnabar and demanded tinder logs lane: tinder, not lane: cinnabar.

Added the two wire-versus-log regression tests you asked for:

  • TestARescueArmRecordsOnlyTheMachineItWentOutTo - a rescue (laneChoice.Only = [cinnabar, tinder], hedgeLane: tinder) captures demandedOnly = [tinder], writes no set, and attributes to tinder.
  • TestANarrowedRetryRecordsTheMachineItReallyDemanded - end-to-end through pacedPair(t, "cinnabar"): the tinder-served retry row carries no set and does not attribute to cinnabar.

Both fail at the previous head and pass now; the original set and emptiness tests still pass, and the census fixture is unchanged (it already counts agreement as served inside the set, which is now populated correctly).

One caveat unchanged from before: I verified on a Windows box, so the two pre-existing failures I noted (the tree-walking canary over !windows packages, and the TempDir cleanup race) are environmental, not from this change.

@7vignesh
7vignesh requested a review from ZeroPoint95 October 14, 2026 10:25
@santoshkumarradha santoshkumarradha added bug Something the code does that it should not area:logging The debug record: call log, trace, journal labels Oct 14, 2026
@santoshkumarradha santoshkumarradha added this to the Reliable agent milestone Oct 14, 2026

@ZeroPoint95 ZeroPoint95 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@7vignesh, the rescue and narrowed-retry changes now pass the independent checks.

Both now pass independent wire-versus-log checks for their start and finish rows. One remaining issue needs fixing:

  1. Preserve the snapshot through automatic closing.
    closeOpenAttempt clears trace.open before calling record, so bodyDemandedOnly falls back to the latest encode’s demand rather than open.demandedOnly.

    A deterministic reproduction using real encodes opens attempt 1 with only: [cinnabar, tinder], then encodes attempt 2 with only: [tinder]. Attempt 2’s start automatically closes attempt 1 as hopped. For the same attempt-1 ID, the start records lane: cinnabar, lanes: [cinnabar, tinder], but its ending records lane: tinder with no set.

    Please preserve the captured snapshot until the closing row is built, and add coverage to the existing unconditional-close regression. This reproduction fails at the new head and passes at the previous head. Ordinary 429 retries explicitly write their endings and pass; this finding concerns the fallback closing mechanism.

  2. Documentation consistency — nonblocking.
    Please remove the obsolete duplicated demandedLanes commentary about reading laneChoice.Only. Also correct the new “[Lane] first” promise: a three-to-two retry can retain the original Lane while Lanes correctly names the remaining pair. The census membership calculation remains correct.

CI is green. Local checks completed; the provider suite had an allocation-law failure also reproduced on dev and a cancellation-test race that passed its focused retry.

… fix docs

ZeroPoint95's second review caught that closeOpenAttempt clears trace.open
before recording, so a finish row built on the fallback close read the latest
encode's demand rather than the closed attempt's. Carry the attempt's snapshot
through recordFacts.demandedOnly and read it off the ended row in
bodyDemandedOnly, keyed on ended (set only on that row) so an attempt closed
carrying no demand is not handed a later one.

Also remove the stale duplicated demandedLanes doc that still described reading
laneChoice.Only, and correct the Record.Lanes doc: Lane is a member of the set
but not necessarily its first element, since a three-to-two retry can keep the
original ranked head as Lane while Lanes names the surviving pair.

Adds TestAHoppedAttemptKeepsItsOwnDemandedSetThroughTheClose, which fails
without the carry and passes with it.
@7vignesh

Copy link
Copy Markdown
Contributor Author

Thanks @ZeroPoint95, both addressed.

1. Preserve the snapshot through the automatic close. You were right: closeOpenAttempt clears trace.open before it records, so bodyDemandedOnly fell through to the trace's last-encoded set, which belongs to a later attempt. Fixed by carrying the closed attempt's snapshot the same way its knobs are carried:

  • recordFacts gains demandedOnly, which closeOpenAttempt fills from open.demandedOnly.
  • bodyDemandedOnly reads it off a row with ended set (the only row the fallback close writes), so an attempt closed carrying no demand is not handed a later attempt's one either - the signal is the carried snapshot, not the field being non-nil.

Added TestAHoppedAttemptKeepsItsOwnDemandedSetThroughTheClose: a closing row whose trace already holds a later [tinder] keeps its own [cinnabar, tinder]. It fails at this change's previous head (took a later attempt's demand: [tinder]) and passes now; the single-no-demand close is asserted too.

2. Documentation. Removed the stale duplicated demandedLanes block that still described reading laneChoice.Only. Corrected the Record.Lanes doc: Lane is a member of the set but not necessarily first, since a three-to-two retry can keep the original ranked head as Lane while Lanes names the surviving pair; relaxed the first test's assertion from Lane == Lanes[0] to Lane being a member accordingly.

On the environment: I verified on Windows, where internal/session can't test-build (!windows dependency) and I hit a TempDir cleanup race on an unrelated provider test that also reproduces on dev. The allocation-law failure and cancellation race you saw on dev are consistent with that; my own four provider tests and the census fixture pass.

@7vignesh
7vignesh requested a review from ZeroPoint95 October 14, 2026 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:logging The debug record: call log, trace, journal bug Something the code does that it should not

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The call row names one machine, so served ≠ asked stopped meaning what it says once the chooser demands a set

3 participants